Language basics

SCL is a functional programming language that is based on lambda calculus. Lambda calculus is a minimal but still Turing complete language with three constructions:

  • Variables
  • Function applications
  • Function definitions (lambda terms)

These are also the most central elements of SCL and most of the other constructions are only syntactic sugar on top of lambda calculus.

SCL syntax is very close to the syntax of Haskell programming language and many tutorials on Haskell apply also to SCL. The main difference between the languages is the evaluation strategy: Haskell evaluates terms lazily, SCL strictly. Unlike Haskell, SCL allows side-effects during function application, but controls them with effect typing. In this respects, it behaves similarly to ML or OCaml.

SCL inherits the following philosophy from Haskell and other programming languages in functional paradigm:

  • Avoid stateful programming
  • Express mathematical problems in mathematical syntax
  • Expressive types prevent runtime errors

This section gives a walkthrough for the most importatant constructs of SCL language.

Literals

SCL supports the following types of constant expressions:

Integers

3423

Floating point numbers

1.2
2.6e-4

String literals

SCL supports single-line strings (enclosed in quotes)

"Single line text"
"Text\nwith\nmultiple lines"

and multi-line strings (enclosed in triple quotes)

"""Text
with
multiple lines"""

Single-line strings may contain escaped characters (\n line feed, \t tabulator, \r carriage return, \uxxxx unicode character, \<character> the character without the first \).

Character literals

't'
'\''

Boolean constants are written as True and False although they are not technically literals (but constructors).

Identifiers

Variable and constant names start with lower case letter followed by letters, digits, _ and '. It customary to write multi-word concepts with camel case convention, for example printError or importA5Model.

The following names are reserved and cannot be used as identifiers: as, by, class, data, deriving, do, effect, else, enforce, extends, forall, hiding, if, import, importJava, in, include, infix, infixl, infixr, instance, let, match, mdo, rule, select, then, transformation, type, when, where, with.

Identifiers starting with upper case letter are constructors and they can be defined only together with the new data types. Examples: True, False and Nothing.

Function applications

A function is applied by writing the function and its parameters consecutively.

sin pi
max 2 5

A parameter needs to be closed in parenthesis if it is not a variable, literal, or an expression starting with if, let, do, etc.

sqrt (x*x + y*y)
atan2 (sin a) (cos a)

For example

sqrt x*x + y*y

is evaluated as

(sqrt x)*x + y*y

because the function application has higher precedence than any binary operator. Parentheses are also needed around negation

sin (-1.42)

because otherwise the expression is tried to compile as

sin - 1.42

causing a compilation error because sin and 1.42 are not compatible for subtraction.

Binary operators

Binary operators are normal functions that are just written between their parameters:

1 + 2

Each binary operator can be converted into ordinary function by putting parentheses around it

(+) 1 2

Similarly an ordinary function can be converted into binary operator by putting backticks (`) around it.

3.4 `max` 4.5

Binary operators have precedences that determines how multiple consecutive binary operators are compiled. For example

1*2+3*4+5*6

is evaluated as

((1*2) + (3*4)) + (5*6)

Variable definitions

Variables are defined by syntax

variableName = variableValue

for example

g = 9.80665

Defined variable values are available in the consecutive expressions:

a = 13
b = 14
a*b

Function definitions

Functions are defined by writing a function application in the left-hand side of the equation:

increaseByOne x = x+1

Defined functions are available in the consecutive expressions:

increaseByOne 13

Conditional expressions

Conditional expressions are written in the form:

if <condition> then <successBranch> else <failureBranch>

The else branch is always mandatory.

abs x = if x > 0
        then x
        else -x

Recursion

A function definition can refer to itself. For example, the Euclidean algorithm for computing the greatest common divisor of two integers can be written as:

gcd a b = if a > b
          then gcd b a
          else if a == 0
          then b
          else gcd (b `mod` a) a

Local definitions

Variable and function definitions can be written as a part of larger expression. There are three language constructs for this purpose:

let <definitions> in <result>

do <definitions> 
   <result>
   
<definition> where <definitions>

They are quite similar and it is usually just a stylistic choice which one to use.

If you are only defining local variables that are needed in a subexpression and their computation does not have side-effects (or has only reading effects), the most natural choice is let-construct that allows you to define variables between let and in and used the variables in the expression following in:

distance (x1,y1) (x2,y2) = let dx = x1-x2
                               dy = y1-y2
                           in sqrt (dx*dx + dy*dy) 

Let-expressions can be freely embedded in other expressions:

"""
Finds a root of f given neg and pos with assumption that
   f neg < 0
and
   f pos > 0 
""" 
bisectionMethod f neg pos =
  let middle = (neg+pos)*0.5 in
    if abs (neg-pos) < 1e-9
    then middle
    else let middleVal = f middle in
      if middleVal < (-1e-9)
      then bisectionMethod f middle pos
      else if middleVal > 1e-9
      then bisectionMethod f neg middle
      else middle

Another construction allowing local variable bindings is do. The value of the whole do-expression is determined by the last expression in the do-block. This construct should be used when there are side-effects involved and you want to mix variable bindings with function applications that ignore their result:

createModel parent name = do
    model = newResource ()
    claim model L0.InstanceOf SIMU.Model
    claimRelatedValue model L0.HasName name
    claim model L0.PartOf parent
    model

The final binding construction is where that locates variable definitions after the position the variables are used. It differs from let and do because it does not define an expression but is always related to some other definition. The benefit is that the same where -block can be used by multiple different guarded definitions:

"""
Returns all solutions of the quadratic equation a*x^2 + b*x + c = 0.
"""
solveQuadratic :: Double -> Double -> Double -> [Double]
solveQuadratic a b c
    | discriminant < 0 = []
    | discriminant > 0 = let sqrtDisc = sqrt discriminant
                         in [ (-b-sqrtDisc)/(2*a)
                            , (-b+sqrtDisc)/(2*a) ]
    | otherwise        = [ -b / (2*a) ]
  where
    discriminant = b*b - 4*a*c

Functions taking other functions as parameters

In SCL, functions are ordinary values that can be stored to variables and manipulated. For example, writing only the function name to the console produces:

> sin
<value of type Double -> <a> Double> 

We can define for example a numerical derivative operation

epsilon = 1e-9
der f x = (f (x + epsilon) - f (x - epsilon)) / (2*epsilon)

Now,

> der sin 0
1.0
> der exp 1
2.7182818218562943

Anonymous function definitions

Functional style of programming favors lots of small functions. Giving a name for all of them becomes quickly tedious and therefore functions can be defined also anonymously. For example

\x -> x+1

defines a function that increases its parameter by one. Thus

(\x -> x+1) 13

returns 14.

Assuming that the function der is defined as above

> der (\x -> x*x) 2
4.000000330961484 

Partial function application

It is possible to give a function less parameters that it accepts:

> der sin
<value of type Double -> <a> Double>

Such a partial application creates a function that expects the missing parameters:

> myCos = der sin
> myCos 0
> myCos pi
-1.000000082740371

It is possible to partially apply also binary operators giving only one of its parameters. Such an application must always be enclosed in parentheses

lessThanOne = (< 1)
greaterThanOne = (1 <)

Pattern matching

The constructors are special kind of values and functions that can be used in the left-hand side of the function and value definitions. Most common constructors are the tuple constructors (), (,), (,,), ...:

toPolarCoordinates (x,y) = (sqrt (x*x + y*y), atan2 y x)
toRectangularCoordinates (r,angle) = (r*cos angle, r*sin angle)

Other constructors are used like functions, but the name of the constructor is capitalized, for example Just and Nothing:

fromMaybe _       (Just v) = v
fromMaybe default Nothing  = default

This example demonstrates also some other features. A function can be defined with multiple equations and the first matching equation is used. Also, if some parameter value is not used, it may be replaced by _.

Indentation